#[derive(Debug)]
pub struct CompUnit {
  pub func_def: FuncDef,
}

impl CompUnit {
  pub fn dump_koopa(&self) -> String {
    self.func_def.dump_koopa()
  }
}

#[derive(Debug)]
pub struct FuncDef {
  pub func_type: FuncType,
  pub ident: String,
  pub block: Block,
}

impl FuncDef {
  pub fn dump_koopa(&self) -> String {
    let func_head = format!("fun @{}(): {} {{", self.ident, self.func_type.koopa_type());
    let func_body = format!("{}", self.block.dump_koopa());
    format!("{func_head}\n{func_body}\n}}")
  }
}

#[derive(Debug)]
pub enum FuncType {
  Int,
}

impl FuncType {
  pub fn koopa_type(&self) -> String {
    match self {
      Self::Int => String::from("i32"),
    }
  }
}

#[derive(Debug)]
pub struct Block {
  pub stmt: Stmt,
}

impl Block {
  pub fn dump_koopa(&self) -> String {
    // hard code for %entry for now
    format!("%entry:\n{}", self.stmt.dump_koopa())
  }
}

#[derive(Debug)]
pub struct Stmt {
  pub exp: Exp,
}

impl Stmt {
  pub fn dump_koopa(&self) -> String {
    match self.exp.dump_koopa().sym_inc {
      Some(num) => {
        format!(
          r#"{}
    ret %{}"#,
          self.exp.dump_koopa().exp_str,
          num
        )
      }
      None => {
        format!("ret {}", self.exp.dump_koopa().exp_str)
      }
    }
  }
}

#[derive(Debug)]
pub struct Exp {
  pub unary_exp: Box<UnaryExp>,
}

impl Exp {
  pub fn dump_koopa(&self) -> ExpRet {
    self.unary_exp.dump_koopa()
  }
}

#[derive(Debug)]
pub enum UnaryExp {
  PrimaryExp(PrimaryExp),
  MUnaryExp { unary_op: UnaryOp, unary_exp: Box<UnaryExp> },
}

impl UnaryExp {
  pub fn dump_koopa(&self) -> ExpRet {
    match self {
      Self::PrimaryExp(primary_exp) => primary_exp.dump_koopa(),
      Self::MUnaryExp { unary_op, unary_exp } => {
        let exp_str: String;
        let mut sym_inc = unary_exp.dump_koopa().sym_inc;
        match unary_op {
          UnaryOp::Positive => {
            // Do nothing
            exp_str = unary_exp.dump_koopa().exp_str;
          }
          UnaryOp::Negative => match sym_inc {
            Some(num) => {
              exp_str = format!(
                r#"{}
  %{} = sub 0, %{}"#,
                unary_exp.dump_koopa().exp_str,
                num + 1,
                num
              );
              sym_inc = Some(num + 1)
            }
            None => {
              exp_str = format!("  %0, sub 0, {}", unary_exp.dump_koopa().exp_str);
              sym_inc = Some(0); // make sym_inc meaningful
            }
          },
          UnaryOp::LogicNot => match sym_inc {
            Some(num) => {
              exp_str = format!(
                r#"{}
  %{} = eq %{}, 0"#,
                unary_exp.dump_koopa().exp_str,
                num + 1,
                num
              );
              sym_inc = Some(num + 1);
            }
            None => {
              exp_str = format!("  %0 = eq {}, 0", unary_exp.dump_koopa().exp_str);
              sym_inc = Some(0); // make sym_inc meaningful
            }
          },
        }
        ExpRet { exp_str, sym_inc }
      }
    }
  }
}

#[derive(Debug)]
pub enum PrimaryExp {
  Exp(Exp),
  Number(i32),
}

pub struct ExpRet {
  exp_str: String,
  sym_inc: Option<u32>,
}

impl PrimaryExp {
  pub fn dump_koopa(&self) -> ExpRet {
    match self {
      Self::Exp(exp) => exp.dump_koopa(),
      Self::Number(num) => ExpRet { exp_str: format!("{num}"), sym_inc: None },
    }
  }
}

#[derive(Debug)]
pub enum UnaryOp {
  Positive,
  Negative,
  LogicNot,
}
